package com.huawei.ds.patch;

import com.intellij.updater.ConsoleUpdaterUI;
import com.intellij.updater.Utils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.pattern.CachedDateFormat;

import javax.xml.transform.stax.StAXResult;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;

public class PatchMain {
    private static final long REQUIRED_FREE_SPACE = 2_000_000_000l;
    private static final long DISTRIBUTION_SPACE = 1_800_000_000l;
    private static File myTempDir;

    private static Logger logger = null;
    private static ConsoleUpdaterUI ui = new ConsoleUpdaterUI();

    public static final String MS_FULL_UPDATER_JAR = "ms-updater-full.jar";

    private static final String OPTION_UPDATER_JAR_DIR = "--updaterJarDir";

    public static final String ACTION_NULL = "null";
    public static final String ACTION_CREATE = "create";
    public static final String ACTION_BATCH_CREATE = "batch-create";
    public static final String ACTION_OTHER = "other";

    public static void main(String[] args) {
        initLogger();
//        wins
//        args = new String[]{"create", "201.8743.12.1", "201.8743.12.6", "D:\\Ascend\\Linux_pack\\201.8743.12.1\\MindStudio_2.0.1.B010.win.zip", "D:\\Ascend\\Linux_pack\\201.8743.12.6\\MindStudio_3.0.1.B010.win.zip",
//        "critical=bin/MindStudio.exe", "critical=bin/MindStudio64.exe", "--zip_as_binary"};
//        args = new String[]{"create", "201.8743.12.1", "201.8743.12.6", "D:\\Ascend\\Linux_pack\\201.8743.12.1\\MindStudio_2.0.1.B010_linux.tar.gz", "D:\\Ascend\\Linux_pack\\201.8743.12.6\\MindStudio_3.0.1.B010_linux.tar.gz",
//                "critical=bin/MindStudio.sh", "critical=bin/format.sh", "--zip_as_binary"};
        logger.info("args:" + Arrays.toString(args));
        startUIProcess("Starting to execute...");
        startUIProcess("args: " + Arrays.toString(args));

        List<String> revisedArgList = new ArrayList<>();
        String currentUpdaterJarDir = getUpdaterJarDir(args, revisedArgList);
//        currentUpdaterJarDir = "D:\\Ascend\\Linux_daPatch_pack\\";
        startUIProcess("currentUpdaterJarDir: " + currentUpdaterJarDir);
        if (currentUpdaterJarDir == null) {
            return;
        }

        args = revisedArgList.toArray(new String[0]);

        try {
            initTempDir();

            if (args.length >= 2 && "batch-create".equals(args[0])) {
                //batch-create
                PatchOptions po = new PatchOptions(ACTION_BATCH_CREATE, args, myTempDir, currentUpdaterJarDir);
                startUIProcess("using updater jar: " + po.getUpdaterJar() + ", --only_to_target_version: " + po.isBatchOnlyToTargetDistributionVersion());;
                batchCreatePatch(po);
            }
            else {
                String action;
                if (ArrayUtils.isEmpty(args)) {
                    action = ACTION_NULL;
                }
                else if (args.length >= 5 && ACTION_CREATE.equals(args[0])) {
                    action = ACTION_CREATE;
                }
                else {
                    action = ACTION_OTHER;
                }
                PatchOptions po = new PatchOptions(action, args, myTempDir, currentUpdaterJarDir);
                startUIProcess("Using updater jar: " + po.getUpdaterJar());
                PatchAdapter.build(po);
            }
        } catch (Exception e) {
            logger.error("MS updater failed: " + e.getMessage());
        } finally {
            try {
                startUIProcess("Cleaning up ds temp files...");
                com.huawei.ds.patch.Utils.delete(myTempDir);
            } catch (Exception e) {
                logger.error("Unable to clean temp dir: " + myTempDir);
            }

            logger.info("Deleted working directory: " + myTempDir.getPath());
            myTempDir = null;
        }
    }

    public static Logger logger() {
        if (logger == null) {
            initLogger();
        }
        return logger;
    }

    public static void batchCreatePatch(PatchOptions po) throws IOException {
        startUIProcess("Starting batch create patch...");
        File distriDir = new File(po.getBatchDistributionDir());
        String targetDistriVersion = po.getBatchTargetDistributionVersion();

        if (!distriDir.exists() || !distriDir.isDirectory()) {
            return;
        }

        Map<String, List<File>> distriMap = collectAllDistributions(distriDir, targetDistriVersion);

        sortByFilename(distriMap);
        BuildStrategy batchBuildStrategy = resolveBuildStrategy(distriMap, po.getTempWorkspaceDir());
        buildPatch(distriMap, po, batchBuildStrategy);
    }

    private static Map<String, List<File>> collectAllDistributions(File dir, String targetDistriVersion) throws IOException {
        Map<String, List<File>> distriMap = new HashMap<>();
        Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                String fileName = file.toFile().getName();
                Pair<String, String> osAndVersionPair = extractOsAndVersion(fileName);
                String osName = osAndVersionPair.getKey();
                String version = osAndVersionPair.getValue();
                if (StringUtils.isBlank(osName)) {
                    return FileVisitResult.CONTINUE;
                }

                if (StringUtils.isBlank(version) || (StringUtils.isNotBlank(targetDistriVersion) && version.compareTo(targetDistriVersion) > 0)) {
                    return FileVisitResult.CONTINUE;
                }

                List<File> list = distriMap.get(osName);
                if (list == null) {
                    list = new ArrayList<>();
                    distriMap.put(osName, list);
                }
                list.add(file.toFile());
                return FileVisitResult.CONTINUE;
            }
        });
        return distriMap;
    }

    private static Pair<String, String> extractOsAndVersion(String fileName) {
        Pair<String, Integer> os = PatchAdapter.resolveOsName(fileName);
        String osName = os.getKey();
        if (StringUtils.isBlank(osName)) {
            return Pair.of(null, null);
        }
        int fileSuffixLen = os.getValue();
        String fileNameWithoutSuffix = fileName.substring(0, fileName.length() - fileSuffixLen);
        int lastDashIndex = fileNameWithoutSuffix.lastIndexOf("-");
        String version = lastDashIndex != -1 ? fileNameWithoutSuffix.substring(lastDashIndex + 1) : "";
        return Pair.of(osName, version);
    }

    private static void sortByFilename(Map<String, List<File>> distriMap) {
        for (List<File> list : distriMap.values()) {
            list.sort(fileNameComparator);
        }
    }

    private static Comparator<File> fileNameComparator = new Comparator<File>() {
        @Override
        public int compare(File o1, File o2) {
            return o1.getName().compareTo(o2.getName());
        }
    };

    public static BuildStrategy resolveBuildStrategy(Map<String, List<File>> distriMap, File tempDir) {
        long usableSpaceInBytes = tempDir.getUsableSpace();
        int distriMaxCountByOs = 0;
        for (List<File> list : distriMap.values()) {
            int countByOs = list.size();
            if (countByOs > distriMaxCountByOs) {
                distriMaxCountByOs = countByOs;
            }
        }

        if (usableSpaceInBytes - distriMaxCountByOs * DISTRIBUTION_SPACE > REQUIRED_FREE_SPACE) {
            return BuildStrategy.BY_OS;
        }
        else {
            return BuildStrategy.BY_ONE;
        }
    }

    private static enum BuildStrategy {
        BY_OS,
        BY_ONE
    }

    private static void buildPatch(Map<String, List<File>> distriMap, PatchOptions po, BuildStrategy batchBuildStrategy) throws IOException {
        File tempWorkspaceDir = po.getTempWorkspaceDir();
        for (Map.Entry<String, List<File>> entry : distriMap.entrySet()) {
            String os = entry.getKey();
            List<File> list = entry.getValue();

            int count = list.size();
            startUIProcess("starting to create patch for os: " + os);
            for (int i = 0; i < count - 1; i++) {
                File leftFile = list.get(i);
                String batchTargetVersion = po.getBatchTargetDistributionVersion();
                if (StringUtils.isNotBlank(batchTargetVersion) && po.isBatchOnlyToTargetDistributionVersion()) {
                    File rightFile = list.get(count - 1);
                    Pair<String, String> osAndVersionPair = extractOsAndVersion(rightFile.getName());
                    String version = osAndVersionPair.getValue();
                    if (StringUtils.equals(version, batchTargetVersion)) {
                        buildPatchForOne(leftFile, rightFile, po, batchBuildStrategy);
                    }
                    continue;
                }
                int next = i + 1;
                while (next < count) {
                    File rightFile = list.get(next);
                    buildPatchForOne(leftFile, rightFile, po, batchBuildStrategy);
                    next++;
                }
            }
            startUIProcess("Clean temp files for os: " + os);
            clearTempDir(tempWorkspaceDir);
        }
    }

    private static void buildPatchForOne(File leftFile, File rightFile, PatchOptions parentPo, BuildStrategy buildStrategy) throws IOException {
        startUIProcess(String.format("Starting to create diff patch for &s <-> %s", leftFile.getName(), rightFile.getName()));
        PatchOptions subPo = buildPatchOptionsForCreate(leftFile, rightFile, parentPo);
        PatchAdapter.build(subPo);

        if (buildStrategy == BuildStrategy.BY_ONE) {
            clearTempDir(parentPo.getTempWorkspaceDir());
        }
    }

    private static PatchOptions buildPatchOptionsForCreate(File leftFile, File rightFile, PatchOptions parentPo) throws IOException {
        List<String> args = new ArrayList<>();
        args.add(ACTION_CREATE);
        args.add("");
        args.add("");
        args.add(leftFile.getCanonicalPath());
        args.add(rightFile.getCanonicalPath());
        args.add("critical=bin/MindStudio.sh");
        args.add("critical=tools/check_env.sh");
        args.add("--zip_as_binary");
        PatchOptions subPo = new PatchOptions(ACTION_CREATE, args.toArray(new String[0]), parentPo.getTempWorkspaceDir(), parentPo.getUpdaterJarDir());
        return subPo;
    }

    private static void clearTempDir(File tempDir) throws IOException {
        if (tempDir == null || !tempDir.exists()) {
            return;
        }
        for (File file : tempDir.listFiles()) {
            com.huawei.ds.patch.Utils.delete(file);
        }
    }

    private static String getUpdaterJarDir(String[] args, List<String> revisedArgList) {
        String localDebugUpdaterDir = null;
        if (args == null) {
            args = ArrayUtils.EMPTY_STRING_ARRAY;
        }
        for (String arg : args) {
            if (arg.startsWith(OPTION_UPDATER_JAR_DIR)) {
                localDebugUpdaterDir = arg.substring(OPTION_UPDATER_JAR_DIR.length() + 1);
                continue;
            }
            revisedArgList.add(arg);
        }

        String currentUpdaterJarDir = getCurrentRunnableJarDir();
        if (currentUpdaterJarDir == null) {
            logger.warn("Updater jar directory is null: there is no lib directory, we will try to get for --updaterJarDir");
            if (localDebugUpdaterDir == null || Files.notExists(Paths.get(localDebugUpdaterDir))) {
                logger.error("Updater jar directory is null or not exists: " + localDebugUpdaterDir);
            }
            currentUpdaterJarDir = localDebugUpdaterDir;
        }
        return currentUpdaterJarDir;
    }

    private static String getCurrentRunnableJarDir() {
        String thisRunnableJarPath = PatchMain.class.getProtectionDomain().getCodeSource().getLocation().getPath();
        int last = thisRunnableJarPath.lastIndexOf("lib");
        return last != -1 ? thisRunnableJarPath.substring(0, last) : null;
    }

    private static void initTempDir() throws IOException {
        if (myTempDir == null) {
            myTempDir = Files.createTempDirectory(Paths.get(Utils.findDirectory(REQUIRED_FREE_SPACE)), "ds.updater.files.").toFile();
        }
    }

    private static void initLogger() {
        if (logger == null) {
            String logDirectory = Utils.findDirectory(1_000_000L);
            String logPath = new File(logDirectory, "ds-updater.log").getAbsolutePath();

            FileAppender update = new FileAppender();
            update.setFile(logPath);
            update.setLayout(new PatternLayout("%d{dd/MM HH:mm:ss} %-5p %C{1}.%H - %m%n"));
            update.setThreshold(Level.ALL);
            update.setAppend(true);
            update.activateOptions();

            FileAppender updateError = new FileAppender();
            update.setFile(new File(logDirectory, "ds_updater_error.log").getAbsolutePath());
            update.setLayout(new PatternLayout("%d{dd/MM HH:mm:ss} %-5p %C{1}.%H - %m%n"));
            update.setThreshold(Level.ERROR);
            update.setAppend(false);
            update.activateOptions();

            logger = Logger.getLogger("com.huawei.ds.patch.PatchCreator");
            logger.addAppender(updateError);
            logger.addAppender(update);
            logger.setLevel(Level.ALL);
            logger.info("--- Updater Started ---");
        }
    }

    public static void startUIProcess(String title) {
        String dt = DateFormatUtils.format(new Date(), "MM-dd HH:mm:ss");
        ui.startProcess(dt + " " + title);
    }
}
